#include "Applejack/body.hpp"
#include "exception.hpp"
#include "Applejack/applejackscript.hpp"
#include "loadedlib.hpp"
#include "Applejack/fixture.hpp"
#include <iostream>
#include <errormanager.hpp>

using namespace Shiny;
using namespace Applejack;

float rad2deg = 180.f / b2_pi;

Body::Body(World& world) : _world(world)
{
  _script = 0;
  _body   = 0;
}

Body::~Body()
{
  _body->SetUserData(0);

  std::list<Applejack::Fixture*> list;
  b2Fixture*                     it = _body->GetFixtureList();

  while (it)
  {
      list.push_back(Applejack::Fixture::GetFixture(it));
      it = it->GetNext();
  }
  while (list.begin() != list.end())
  {
      if (*list.begin() != 0)
	delete (*list.begin());
      list.erase(list.begin());
  }
  if (_body)   _world.DestroyBody(_body);
  if (_script) delete _script;
}

void Body::DeleteLater()
{
  _world.DeleteBody(this);
}

void Body::UpdateGraphics(void)
{
  const b2Vec2       pos   = _body->GetPosition();
  const float        angle = _body->GetAngle();
  const unsigned int ratio = _world.Ratio();

  _sprite.SetPosition(pos.x * ratio, pos.y * ratio);
  _sprite.SetRotation(angle * rad2deg);
  _sprite.Update();
}

void Body::UpdateLogic(void)
{
  if (_script)
    _script->Run();
}

Applejack::Fixture* Body::Fixture(const std::string& name)
{
  b2Fixture* fixture = _body->GetFixtureList();

  while (fixture)
  {
    Applejack::Fixture* fixtureObj = reinterpret_cast<Applejack::Fixture*>(fixture->GetUserData());

    if (fixtureObj != 0 && *fixtureObj == name)
      return (fixtureObj);
    fixture = fixture->GetNext();
  }
  return (0);
}

void Body::SetIA(const std::string& source, const std::string& type)
{
  LibList&                     libs   = _world.GetLibList();
  const Utils::LoadedLib*      lib    = libs[source];

  if (!lib)
    lib = libs.LoadLib(source, "./scripts/" + source + LIB_EXTENSION);
  if (lib)
  {
    IA::factoryFunction function;

    function = lib->GetFunction<IA::factoryFunction>("New");
    if (function)
    {
      if (_script)
        delete _script;
      _script = function(_world, *this);
    }
    else
      std::cerr << "Can't load script: " << lib->GetLastError() << std::endl;
  }
}

b2Vec2 Body::PhysicPoint(sf::Vector2f position)
{
    float       ratio = _world.Ratio();
    sf::IntRect rect  = _sprite.GetTextureRect();
    b2Vec2      point;

    point.x = (rect.Width  - position.x) / ratio;
    point.y = (rect.Height - position.y) / ratio;
    return (point);
}

/*
 * Body::Factory
 */
Body* Body::Factory::New(World& world, const std::string& type, sf::Vector2f position, float angle)
{
  Body*                  body  = 0;
  b2BodyDef              def;
  const ApplejackObject* object;
  sf::Vector2f           halfSize;

  object = world.GetObjects()[type];
  if (object == 0)
  {
    ErrorManager::Get()->AddError("Requested object '" + type + "' isn't registered for the current world");
    return (0);
  }
  body = new Body(world);

  std::list<b2FixtureDef>::const_iterator itFixture     = object->fixtures.begin();
  std::list<b2FixtureDef>::const_iterator endFixture    = object->fixtures.end();

  body->_sprite.SetAnimations(object->graphics);
  body->_sprite.SetAnimation(0);
  body->_sprite.Update();
  sf::IntRect rect = body->_sprite.GetTextureRect();
  halfSize.x = rect.Width  / 2;
  halfSize.y = rect.Height / 2;
  body->_sprite.SetOrigin(halfSize);

  def.position.Set((position.x + halfSize.x) / world.Ratio(), (position.y + halfSize.y) / world.Ratio());
  def.type          = object->type;
  def.angle         = angle / rad2deg;
  def.fixedRotation = object->hasFixedRotation;
  def.bullet        = object->isBullet;
  body->_body       = world.CreateBody(&def);
  body->_body->SetUserData(body);
  for (; itFixture != endFixture ; ++itFixture)
  {
    b2Fixture*  listFixture = 0;
    void*       fixtureData = (*itFixture).userData;

    if (fixtureData)
    {
	std::string defName = reinterpret_cast<const char*>(fixtureData);

	body->_body->CreateFixture(&(*itFixture));
	listFixture = body->_body->GetFixtureList();
	while (listFixture)
	{
	  std::string name  = reinterpret_cast<const char*>(listFixture->GetUserData());

	  if (name == defName)
	  {
	    listFixture->SetUserData(new Applejack::Fixture(defName, listFixture));
	    break ;
	  }
	  listFixture = listFixture->GetNext();
	}
    }
    else
	std::cerr << "!FATAL ERROR! One fixture definition for object " << type << "seems to lack a name." << std::endl;
  }
  return (body);
}

Body* Body::Factory::New(World& world, Data desc)
{
  Body*                  body  = 0;
  b2BodyDef              def;
  const ApplejackObject* object;
  Data                   data, fieldPosition, fieldScript;
  sf::Vector2f           position;
  sf::Vector2f           halfSize;
  float                  angle = 0;

  if (desc.Nil())
  {
    ErrorManager::Get()->AddError("Passed empty DataBranch to Body::Factory::New");
    return (0);
  }
  data          = desc["type"];
  fieldPosition = desc["position"];
  fieldScript   = desc["script"];
  if (data.Nil())
  {
    ErrorManager::Get()->AddError(desc.Key() + ": Missing 'type' field from body description");
    return (0);
  }
  if (not fieldPosition.Nil())
  {
    position.x = fieldPosition["x"];
    position.y = fieldPosition["y"];
    angle      = fieldPosition["angle"];
  }

  body = New(world, data.Value(), position, angle);

  if (fieldScript)
  {
    Data source = fieldScript["source"];
    Data type   = fieldScript["type"];

    if ((not source.Nil()) && (not type.Nil()))
      body->SetIA(source.Value(), type.Value());
  }
  return (body);
}
